#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "accesslist.h"
#include "buffer.h"
#include "base64.h"

#define ACCESS_ALLOC 20

static int search (accesslist_t *accesslist, const char *path, size_t len, unsigned int *position);
static int allocate (accesslist_t *accesslist);

void accesslist_init (accesslist_t *accesslist)
{
	accesslist->records = NULL;
	accesslist->index = NULL;

	accesslist->size = 0;
	accesslist->used = 0;
}

void accesslist_free (accesslist_t *accesslist)
{
	size_t i;

	if (accesslist->records) {
		for (i = 0; i < accesslist->used; i++) {
			stringlist_free (&(accesslist->records[i].authlist));
		}

		free (accesslist->records);
		accesslist->records = NULL;
	}

	if (accesslist->index) {
		free (accesslist->index);
		accesslist->index = NULL;
	}

	accesslist->size = 0;
	accesslist->used = 0;
}

int accesslist_load (accesslist_t *accesslist, configuration_t *conf, const char *host_name)
{
	buffer_t input;
	buffer_t output;
	const char *user;
	const char *password;
	const char *path;
	size_t len;
	size_t i, j;

	/* Free previous configuration. */
	accesslist_free (accesslist);

	buffer_init (&input, 64);
	buffer_init (&output, 64);

	/* For each path... */
	for (i = 0; ((path = configuration_get_child (conf, i, "VirtualHosts", host_name, "AccessList", NULL)) != NULL); i++) {
		/* For each user of the path... */
		for (j = 0; ((user = configuration_get_child (conf, j, "VirtualHosts", host_name, "AccessList", path, NULL)) != NULL); j++) {
			/* Get user's password. */
			if (((password = configuration_get_value (conf, "VirtualHosts", host_name, "Users", user, NULL)) == NULL) || (!*password)) {
				fprintf (stderr, "User %s doesn't have password.\n", user);
				continue;
			}

			buffer_reset (&input);
			buffer_reset (&output);

			if (buffer_format (&input, "%s:%s\0", user, password) < 0) {
				buffer_free (&input);
				buffer_free (&output);

				fprintf (stderr, "Couldn't allocate memory for %s:%s.\n", user, password);
				return -1;
			}

			if (buffer_allocate (&output, (input.used * 2) + 1) < 0) {
				buffer_free (&input);
				buffer_free (&output);

				fprintf (stderr, "Couldn't allocate memory for %s:%s.\n", user, password);
				return -1;
			}

			len = base64_encode (input.data, input.used, output.data, 0);
			if (accesslist_insert (accesslist, path, strlen (path), output.data, len) < 0) {
				buffer_free (&input);
				buffer_free (&output);

				fprintf (stderr, "Couldn't allocate memory for %s:%s.\n", user, password);
				return -1;
			}
		}
	}

	buffer_free (&input);
	buffer_free (&output);

	return 0;
}

int search (accesslist_t *accesslist, const char *path, size_t len, unsigned int *position)
{
	access_t *record;
	int i, j, pivot;
	int ret;

	i = 0;
	j = accesslist->used - 1;

	while (i <= j) {
		pivot = (i + j) / 2;
		record = &(accesslist->records[accesslist->index[pivot]]);

		ret = strncmp (path, record->path, len);
		if (ret < 0) {
			j = pivot - 1;
		} else if (ret == 0) {
			if (len < record->len) {
				j = pivot - 1;
			} else if (len == record->len) {
				*position = (unsigned int) pivot;
				return 0;
			} else {
				i = pivot + 1;
			}
		} else {
			i = pivot + 1;
		}
	}

	*position = (unsigned int) i;

	return -1;
}

int allocate (accesslist_t *accesslist)
{
	access_t *records;
	off_t *index;
	size_t size;

	if (accesslist->used == accesslist->size) {
		size = accesslist->size + ACCESS_ALLOC;
		index = (off_t *) malloc (size * sizeof (off_t));
		if (!index) {
			return -1;
		}

		records = (access_t *) realloc (accesslist->records, size * sizeof (access_t));
		if (!records) {
			free (index);
			return -1;
		}

		if (accesslist->index) {
			memcpy (index, accesslist->index, accesslist->used * sizeof (off_t));
			free (accesslist->index);
		}

		accesslist->records = records;
		accesslist->index = index;
		accesslist->size = size;
	}

	return 0;
}

int accesslist_insert (accesslist_t *accesslist, const char *path, size_t pathlen, const char *auth, size_t authlen)
{
	access_t *access;
	unsigned int position;

	if ((*path != '/') || (pathlen >= PATH_MAX)) {
		return -1;
	}

	/* If not root directory... */
	if (pathlen > 1) {
		/* Remove trailing '/' (if present). */
		if (path[pathlen - 1] == '/') {
			pathlen--;
		}
	}

	if (search (accesslist, path, pathlen, &position) < 0) {
		if (allocate (accesslist) < 0) {
			return -1;
		}

		access = &(accesslist->records[accesslist->used]);

		memcpy (access->path, path, pathlen);
		access->path[pathlen] = 0;
		access->len = pathlen;

		stringlist_init (&access->authlist);

		if (position < accesslist->used) {
			memmove (&(accesslist->index[position + 1]), &(accesslist->index[position]), (accesslist->used - position) * sizeof (off_t));
		}

		accesslist->index[position] = accesslist->used++;
	} else {
		access = &(accesslist->records[accesslist->index[position]]);
	}

	return stringlist_insert_bounded_string (&access->authlist, auth, authlen, 0);
}

int accesslist_allow (accesslist_t *accesslist, const char *path, size_t pathlen, const char *auth, size_t authlen)
{
	access_t *access;
	unsigned int position;

	/* If not root directory... */
	if (pathlen > 1) {
		/* Remove trailing '/' (if present). */
		if (path[pathlen - 1] == '/') {
			pathlen--;
		}
	}

	if (!auth) {
		auth = "";
	}

	do {
		if (search (accesslist, path, pathlen, &position) == 0) {
			access = &(accesslist->records[accesslist->index[position]]);
			if (stringlist_search_bounded_string (&access->authlist, auth, authlen, NULL) < 0) {
				/* Not allowed. */
				return 0;
			}

			/* Allowed. */
			return 1;
		}

		/* Root directory? */
		if (pathlen == 1) {
			/* Allowed. */
			return 1;
		}

		do {
			pathlen--;
			if (path[pathlen - 1] == '/') {
				break;
			}
		} while (1);

		/* If not root directory... */
		if (pathlen > 1) {
			pathlen--;
		}
	} while (1);
}
